package edu.dublbo.app.generator.mapper;

import edu.dublbo.app.generator.advice.exception.OptErrorException;
import edu.dublbo.app.generator.entity.TBasicModel;
import edu.dublbo.app.generator.entity.TBasicModelDetail;
import edu.dublbo.app.generator.result.OptStatus;
import edu.dublbo.app.generator.util.Constant;
import edu.dublbo.app.generator.util.DemoUtils;
import edu.dublbo.app.generator.util.FileOperator;
import edu.dublbo.app.generator.util.StrUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * @author DubLBo, 2020-09-08 20:35
 */
public class CodeFileGenDAO {
    private static final Logger logger = LoggerFactory.getLogger(CodeFileGenDAO.class);

    // 时间格式
    private static final String DATE_FORMAT = "yyyy年M月d日 H:m:s.S";
    // 模板名

    private String[] tpls;

    //    private String author;
    private final String projectName;
    private final String projectDesc;
    private final String curTime;
    private final String packageName;

    public CodeFileGenDAO(String projectName, String projectDesc, String packageName) {
        this.projectName = projectName;
        this.curTime = new SimpleDateFormat(DATE_FORMAT).format(new Date());
        this.packageName = packageName;
        this.projectDesc = projectDesc == null ? "" : projectDesc;
    }

    private String getRequestPath(String moduleName, String busiName) {
        StringBuilder sb = new StringBuilder();
        if (!busiName.toLowerCase().startsWith(moduleName))
            sb.append("/").append(moduleName);

        for (char c : busiName.toCharArray()) {
            if (c >= 'A' && c <= 'Z') {
                sb.append("/").append((char) (c + 32));
            } else {
                sb.append(c);
            }
        }
        return sb.length() == 0 ? "/" : sb.toString();
    }

    /**
     * 获取文件名
     */
    private String getFileName(String tempPath,
                               String modelName,
                               String busiName,
                               String packageName,
                               String moduleName) {
        String packagePath =
                String.format("%s/src/main/java/%s", projectName, packageName.replace('.', '/'));

        if (tempPath.endsWith(Constant.FILE_NAME_TABLE_TF)) {
            return packagePath + "/entity/" + modelName + "Mysql.sql";
        }

        if (tempPath.endsWith(Constant.FILE_NAME_ENTITY_TF)) {
            return packagePath + "/entity/" + modelName + ".java";
        }

        if (tempPath.endsWith(Constant.FILE_NAME_MAPPER_INTER_TF)) {
            return packagePath + "/mapper/" + modelName + "Mapper.java";
        }

        if (tempPath.endsWith(Constant.FILE_NAME_MAPPER_XML_TF)) {
            return String.format("%s/src/main/resources/mapper/mysql/%s/%sMapper.xml",
                    projectName, moduleName, modelName);
        }

        if (tempPath.endsWith(Constant.FILE_NAME_SERVICE_TF)) {
            return packagePath + "/service/" + busiName + "Service.java";
        }

        if (tempPath.endsWith(Constant.FILE_NAME_CONTROLLER_TF)) {
            return packagePath + "/controller/" + busiName + "Controller.java";
        }

        return "";
    }

    public void loadTplFileContent() {
        int n = Constant.FILE_PATH_TEMPLATES.length;
        this.tpls = new String[n];
        for (int i = 0; i < n; ++i) {
            try {
                tpls[i] = FileOperator.readContentLine(Constant.FILE_PATH_TEMPLATES[i]);
            } catch (IOException e) {
                logger.error("读取模板文件【{}】发生错误", tpls[i]);
                throw new OptErrorException(OptStatus.FAIL.code, "读取模板文件错误");
            }
        }
    }

    private String execTagReplacement(String tpl, String[] tags, String... rps) {
        int n = rps.length;
        for (int i = 0; i < n; ++i) {
            tpl = tpl.replace(tags[i], rps[i]);
        }
        return tpl;
    }

    /**
     * 生成代码并压缩，包括：
     * <pre>
     *      1. table 脚本
     *      2. java 实体类
     *      3. mapper 层：mapper接口类 和 mapper xml文件
     *      4. service 类
     *      5. controller 类
     * </pre>
     *
     * @param moduleName 模块名
     * @param model      模型信息
     * @param details    模型明细列表：属性 - 表字段
     * @param zos        压缩文件输出流
     */
    public void zipCode(String moduleName, TBasicModel model, List<TBasicModelDetail> details, ZipOutputStream zos) {
        String modelName = model.getModelName();
        String mPackageName = packageName + "." + moduleName;
        String busiName = DemoUtils.genBusiBeanName(modelName);
        Map<String, String> map = new HashMap<>();
        map.put("modelName", modelName);
        map.put("tableName", model.getTableName());
        map.put("requestPath", getRequestPath(moduleName, busiName));
        map.put("mapperName", modelName + "Mapper");
        map.put("serviceName", busiName + "Service");
        map.put("controllerName", busiName + "Controller");
        map.put("remark", model.getRemark());
        map.put("packageDir", mPackageName);

        try {
            for (int i = Constant.INDEX_CONTROLLER_TF; i >= Constant.INDEX_TABLE_TF; --i) {
                String filename = getFileName(Constant.FILE_PATH_TEMPLATES[i], modelName, busiName, mPackageName, moduleName);
                if (StrUtils.isEmpty(filename)) {
                    logger.error("获取模板【{}】对应的生成文件的文件名错误", Constant.FILE_PATH_TEMPLATES[i]);
                    throw new OptErrorException(OptStatus.FAIL.code, "获取生成文件的文件名错误");
                }
                // 添加到 zip
                zos.putNextEntry(new ZipEntry(filename));
                IOUtils.write(generateCode(i, map, details), zos, StandardCharsets.UTF_8);
                zos.closeEntry();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new OptErrorException(OptStatus.FAIL.code, "压缩文件写入流错误");
        }
    }

    private String generateCode(int fileNo, Map<String, String> dataMap, List<TBasicModelDetail> details) {
        switch (fileNo) {
            case Constant.INDEX_TABLE_TF:
                return generateTableCode(
                        dataMap.get("tableName"),
                        dataMap.get("remark"),
                        details);
            case Constant.INDEX_ENTITY_TF:
                return generateEntityCode(
                        dataMap.get("modelName"),
                        dataMap.get("remark"),
                        dataMap.get("packageDir"),
                        details);
            case Constant.INDEX_MAPPER_INTER_TF:
                return generateMapperInterCode(
                        dataMap.get("modelName"),
                        dataMap.get("mapperName"),
                        dataMap.get("remark"),
                        dataMap.get("packageDir"));
            case Constant.INDEX_MAPPER_XML_TF:
                return generateMapperXmlCode(
                        dataMap.get("modelName"),
                        dataMap.get("mapperName"),
                        dataMap.get("tableName"),
                        dataMap.get("packageDir"),
                        details);
            case Constant.INDEX_SERVICE_TF:
                return generateServiceCode(
                        dataMap.get("modelName"),
                        dataMap.get("mapperName"),
                        dataMap.get("serviceName"),
                        dataMap.get("remark"),
                        dataMap.get("packageDir"));
            case Constant.INDEX_CONTROLLER_TF:
                return generateControllerCode(
                        dataMap.get("modelName"),
                        dataMap.get("serviceName"),
                        dataMap.get("controllerName"),
                        dataMap.get("requestPath"),
                        dataMap.get("remark"),
                        dataMap.get("packageDir")
                );
            default:
                logger.error("未知的模版序号：{}", fileNo);
        }

        return "";
    }

    // 生成 Table structure 的代码
    public String generateTableCode(String tableName, String remark, List<TBasicModelDetail> details) {
        StringBuilder colDeclare = new StringBuilder();
        for (int i = 0; i < details.size(); ++i) {
            TBasicModelDetail de = details.get(i);
            // 重新设置 column type，主要增加的长度
            String ty = de.getColType();
            int len = de.getColumnLength() == null ? 0 : de.getColumnLength();
            int defLen = de.getDefaultColLen() == null ? 0 : de.getDefaultColLen();
            ty = len > 0 ? String.format("%s(%d)", ty, len) :
                    (defLen > 0 ? String.format("%s(%d)", ty, defLen) : ty);
            // String s = String.format("`%s` %s COMMENT \"%s\",", de.getColumnName(), de.getColType(), de.getRemark());
            colDeclare.append("`").append(de.getColumnName()).append("` ")
                    .append(ty).append(" COMMENT \"").append(de.getRemark()).append("\",");
            if (i == details.size() - 1)
                break;
            colDeclare.append("\n\t");
        }

        return execTagReplacement(
                tpls[Constant.INDEX_TABLE_TF],
                Constant.TAGS_TABLE_TF,
                Constant.CODE_AUTHOR,
                curTime,
                remark,
                tableName,
                colDeclare.toString()
        );
    }

    // 生成 Model 层源码
    public String generateEntityCode(String modelName, String remark, String packageDir, List<TBasicModelDetail> details) {
        StringBuilder var1 = new StringBuilder();

        StringBuilder propertyDeclare = new StringBuilder();
        StringBuilder getAndSet = new StringBuilder();
        StringBuilder toString = new StringBuilder();

        getAndSet.append("\n\t");
        toString.append(String.format("\"%s{\" +\n", modelName));
        int iMax = details.size() - 1;
        for (int i = 0; ; ++i) {
            TBasicModelDetail de = details.get(i);
            String py = de.getProType();
            String p = de.getPropertyName();
            if (!de.getQualifiedProType().startsWith("java.lang")) {
                var1.append("import ").append(de.getQualifiedProType()).append(";\n");
            }
            propertyDeclare.append("// ").append(de.getRemark())
                    .append("\n\tprivate ").append(py).append(" ").append(p).append(";");

            getAndSet.append("public ").append(py).append(" ")
                    .append(DemoUtils.field2GetMethodName(p))
                    .append("() {\n\t\treturn ").append(p)
                    .append(";\n\t}\n\n\tpublic void ")
                    .append(DemoUtils.field2SetMethodName(p)).append("(")
                    .append(py).append(" ").append(p)
                    .append(") {\n\t\tthis.").append(p).append(" = ").append(p)
                    .append(";\n\t}");
            if (i > 0) {
                toString.append("\t\t\t\t\", ");
            } else {
                toString.append("\t\t\t\t\"");
            }
            toString.append(calc(py, p)).append("\n");
            if (i == iMax) {
                break;
            }
            propertyDeclare.append("\n\t");
            getAndSet.append("\n\n\t");
        }
        toString.append("\t\t\t\t'}'");

        return execTagReplacement(
                tpls[Constant.INDEX_ENTITY_TF],
                Constant.TAGS_ENTITY_TF,
                packageDir,
                var1.toString(),
                remark,
                Constant.CODE_AUTHOR,
                curTime,
                modelName,
                propertyDeclare.toString(),
                getAndSet.toString(),
                toString.toString());
    }

    private String calc(String typ, String p) {
        if ("String".equals(typ)) {
            return String.format("%s='\" + %s + '\\'' + ", p, p);
        } else {
            return String.format("%s=\" + %s + ", p, p);
        }
    }

    // 生成 Mapper Interface 层内容
    public String generateMapperInterCode(String modelName, String mapperName, String modelRemark, String packageDir) {
        return execTagReplacement(
                tpls[Constant.INDEX_MAPPER_INTER_TF],
                Constant.TAGS_MAPPER_INTER_TF,
                packageDir,
                modelName,
                modelRemark,
                Constant.CODE_AUTHOR,
                curTime,
                mapperName
        );
    }

    // 生成 Mapper Xml 文件内容
    public String generateMapperXmlCode(String modelName, String mapperName, String tableName, String packageDir, List<TBasicModelDetail> details) {
        StringBuilder columnNameItems = new StringBuilder();
        StringBuilder resultMapZone = new StringBuilder();
        StringBuilder conditionZone = new StringBuilder();
        StringBuilder aliasConditionZone = new StringBuilder();
        StringBuilder insertColItems = new StringBuilder();
        StringBuilder updateColItems = new StringBuilder();
        int n = details.size();
        for (int i = 0; i < n; i++) {
            // 列名
            String cname = details.get(i).getColumnName();
            String pname = details.get(i).getPropertyName();
            columnNameItems.append(cname);
            resultMapZone.append("<result column=\"").append(cname).append("\" property=\"").append(pname).append("\"/>");
            conditionZone.append("<if test=\"").append(pname)
                    .append(" != null and ").append(pname)
                    .append(" != ''\">\n\t\t\tAND ").append(cname)
                    .append(" = #{").append(pname).append("}\n\t\t</if>");
            aliasConditionZone.append("<if test=\"").append(pname)
                    .append(" != null and ").append(pname)
                    .append(" != ''\">\n\t\t\tAND a.").append(cname)
                    .append(" = #{").append(pname).append("}\n\t\t</if>");
            insertColItems.append("#{").append(pname).append("}");
            // 全字段更新，不带判断
            updateColItems.append(cname).append(" = #{").append(pname).append("}");
            if ((i + 1) % 5 == 0 && i != n - 1) {
                //添加换行符号
                columnNameItems.append("\n\t\t");
                insertColItems.append("\n\t\t\t");
            }
            if (i == n - 1) {
                break;
            }
            columnNameItems.append(",");
            resultMapZone.append("\n\t\t");
            conditionZone.append("\n\t\t");
            aliasConditionZone.append("\n\t\t");
            insertColItems.append(",");
            updateColItems.append(",\n\t\t\t");
        }

        return execTagReplacement(
                tpls[Constant.INDEX_MAPPER_XML_TF],
                Constant.TAGS_MAPPER_XML_TF,
                packageDir,
                mapperName,
                tableName,
                columnNameItems.toString(),
                modelName,
                resultMapZone.toString(),
                conditionZone.toString(),
                aliasConditionZone.toString(),
                insertColItems.toString(),
                updateColItems.toString()
        );
    }

    // 生成 Service层代码
    public String generateServiceCode(String modelName,
                                      String mapperName,
                                      String serviceName,
                                      String remark,
                                      String packageDir) {
        return execTagReplacement(
                tpls[Constant.INDEX_SERVICE_TF],
                Constant.TAGS_SERVICE_TF,
                packageDir,
                modelName,
                mapperName,
                remark,
                Constant.CODE_AUTHOR,
                curTime,
                serviceName
        );
    }

    public String generateControllerCode(String modelName,
                                         String serviceName,
                                         String controllerName,
                                         String requestPath,
                                         String remark,
                                         String packageDir) {
        return execTagReplacement(
                tpls[Constant.INDEX_CONTROLLER_TF],
                Constant.TAGS_CONTROLLER_TF,
                packageDir,
                modelName,
                serviceName,
                remark,
                Constant.CODE_AUTHOR,
                curTime,
                requestPath,
                controllerName
        );
    }

    /**
     * 压缩整个项目的配置文件
     * <pre>
     *     <li>1. pom.xml</li>
     *     <li>2. application.xml</li>
     * </pre>
     */
    public void zipProjectConfigCode(ZipOutputStream zos) {
        try {
            for (int i = Constant.INDEX_POM_TF; i <= Constant.INDEX_MYBATIS_TF; ++i) {
                String filename = getConfigFileName(Constant.FILE_PATH_TEMPLATES[i]);
                if (StrUtils.isEmpty(filename)) {
                    throw new OptErrorException(OptStatus.FAIL.code, "文件名获取失败");
                }
                // 添加到 zip
                zos.putNextEntry(new ZipEntry(filename));
                IOUtils.write(generateConfigCode(i), zos, StandardCharsets.UTF_8);
                zos.closeEntry();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new OptErrorException(OptStatus.FAIL.code, "压缩文件写入流错误");
        }
    }

    private String getConfigFileName(String tempPath) {
        if (tempPath.endsWith(Constant.FILE_NAME_POM_TF)) {
            return projectName + "/pom.xml";
        }

        if (tempPath.endsWith(Constant.FILE_NAME_APP_CONFIG_TF)) {
            return projectName + "/src/main/resources/application.yml";
        }

        if (tempPath.endsWith(Constant.FILE_NAME_MYBATIS_CONFIG_TF)) {
            return projectName + "/src/main/resources/config/mybatis.xml";
        }
        return null;
    }

    private String generateConfigCode(int fileNo) {
        Map<String, String> m = new HashMap<>();
        switch (fileNo) {
            case Constant.INDEX_POM_TF:
                m.put("#{projectName}", projectName);
                m.put("#{orgName}", packageName);
                m.put("#{projectDesc}", projectDesc);
                return execTagReplacement(tpls[fileNo], m);
            case Constant.INDEX_APP_CONFIG_TF:
                m.put("#{projectName}", projectName);
                m.put("#{packageName}", packageName);
                return execTagReplacement(tpls[fileNo], m);
            case Constant.INDEX_MYBATIS_TF:
                return execTagReplacement(tpls[fileNo], m);
            default:
                logger.warn("未知的模板文件序号：{}", fileNo);
        }
        return "";
    }

    private String execTagReplacement(String tpl, Map<String, String> tagMap) {
        for (Map.Entry<String, String> e : tagMap.entrySet()) {
            tpl = tpl.replace(e.getKey(), e.getValue());
        }
        return tpl;
    }
}
